home *** CD-ROM | disk | FTP | other *** search
/ Developer CD Series 1994…tember: Reference Library / Dev.CD Sep 94.toast / Periodicals / develop / develop Issue 6 / develop 6 code / TCP / NewsWatcher / NewsWatcher 2.0d15 source / source / nntp.c < prev    next >
Encoding:
C/C++ Source or Header  |  1993-08-31  |  49.4 KB  |  1,833 lines  |  [TEXT/KAHL]

  1. /*----------------------------------------------------------------------------
  2.  
  3.     nntp.c
  4.  
  5.     This module handles all transactions with the NNTP server.
  6.     
  7.     Portions copyright © 1990, Apple Computer.
  8.     Portions copyright © 1993, Northwestern University.
  9.  
  10. ----------------------------------------------------------------------------*/
  11.  
  12. #include <stdlib.h>
  13. #include <stdio.h>
  14. #include <string.h>
  15. #include <ctype.h>
  16. #include <errno.h>
  17.  
  18. #include "MacTCPCommonTypes.h"
  19. #include "TCPPB.h"
  20. #include "AddressXlation.h"
  21. #include "packages.h"
  22.  
  23. #include "glob.h"
  24. #include "dlgutil.h"
  25. #include "header.h"
  26. #include "nntp.h"
  27. #include "qsort.h"
  28. #include "tcp.h"
  29. #include "util.h"
  30. #include "tcplow.h"
  31. #include "menus.h"
  32. #include "log.h"
  33.  
  34.  
  35.  
  36. #define kServerInfoDlg                141            /* Server info dialog */
  37. #define kServerInfoTheInfoItem        3
  38.  
  39.  
  40.  
  41. static unsigned long    gNNTPAddr;            /* IP address of NNTP server */
  42. static unsigned long    gConnID = 0;        /* connection id for nntp connection */
  43. static char             *gBuffer;            /* NNTP buffer */
  44. static CStr255            gCurGroup;            /* current group on server */
  45. static CStr255            gHello = "";        /* saved server "hello" message */
  46. static Handle            gServerInfoText;     /* handle to server info text */
  47.  
  48. /* The following are used to close idle news server connections asynchronously. */
  49.  
  50. typedef struct TQueuedClosingNewsConnection {
  51.     TCPiopb pBlock;                /* PBControl param block */
  52.     long myA5;                    /* NewsWatcher's A5 register */
  53.     long connID;                /* connection id */
  54.     short ioCRefNum;            /* MacTCP ioCRefNum */
  55.     Boolean destroy;            /* true if PBControl call error - destroy this connection */
  56.     Boolean finished;            /* true when stream completely torn down and closed */
  57.     char sendBuf[7];            /* buffer for sending "QUIT" command */
  58.     char receiveBuf[256];        /* buffer for receiving final data from news server */
  59.     struct wdsEntry wds[2];        /* WDS for sending "QUIT" command */
  60.     struct TQueuedClosingNewsConnection *next;        /* pointer to next queue element */
  61.     long startingTick;            /* tick count when close operation started (for testing) */
  62. } TQueuedClosingNewsConnection;
  63.  
  64. static TQueuedClosingNewsConnection *gClosingNewsConnectionQueue = nil;  /* stream closing queue */
  65.  
  66. static unsigned long gLastNewsServerTransactionTime;    /* time of last transaction */
  67.  
  68.  
  69.  
  70. /*----------------------------------------------------------------------------
  71.     InitMacTCP 
  72.     
  73.     Initialize MacTCP and allocate the NNTP buffer.
  74.     
  75.     Exit:    function result = true if MacTCP opened, false if error.
  76.             gBuffer allocated.
  77. ----------------------------------------------------------------------------*/
  78.  
  79. static Boolean InitMacTCP (void)
  80. {
  81.     StatusWindow("Opening MacTCP.");
  82.     if (InitNetwork() != noErr) {
  83.         ErrorMessage("Could not open MacTCP.");
  84.         return false;
  85.     }
  86.     gBuffer = MyNewPtr(kBufLen);
  87.     return true;
  88. }
  89.  
  90.  
  91.  
  92. /*----------------------------------------------------------------------------
  93.     AbortNewsConnection 
  94.     
  95.     Aborts a connection to the remote NNTP server.
  96.     
  97.     Entry:    gConnID = connection id of NNTP stream.
  98.     
  99.     Exit:    function result = true if no error, false if error or canceled.
  100.             gConnID = 0.
  101. ----------------------------------------------------------------------------*/
  102.  
  103. static void AbortNewsConnection (void)
  104. {
  105.     if (gConnID == 0) return;
  106.     AbortConnection(gConnID);
  107.     ReleaseStream(gConnID);
  108.     gConnID = 0;
  109. }
  110.  
  111.  
  112.  
  113. /*----------------------------------------------------------------------------
  114.     GetNNTPAddr 
  115.     
  116.     Get the news server IP address, given its name from the preferences file.
  117.     
  118.     Exit:    function result = true if no error, false if error.
  119.             gNNTPAddr = IP address of NNTP server.
  120. ----------------------------------------------------------------------------*/
  121.  
  122. static Boolean GetNNTPAddr (void)
  123. {
  124.     OSErr err;
  125.     
  126.     StatusWindow("Getting news server address.");
  127.     p2cstr(gPrefs.newsServerName);
  128.     err = IPNameToAddr((char*)gPrefs.newsServerName, &gNNTPAddr);
  129.     c2pstr((char*)gPrefs.newsServerName);
  130.     if (err != noErr) {
  131.         ErrorMessage("Could not get news server address.");
  132.         return false;
  133.     }
  134.     return true;
  135. }
  136.  
  137.  
  138.  
  139. /*----------------------------------------------------------------------------
  140.     ParseNum 
  141.     
  142.     Parse a number from a string.
  143.  
  144.     Entry:    *p = pointer to first char of number.
  145.  
  146.     Exit:    function result = parsed number, or -1 if error.
  147.             *p = pointer to first non-white space char following number.
  148. ----------------------------------------------------------------------------*/
  149.  
  150. static long ParseNum (char **p)
  151. {
  152.     long num;
  153.     char *endp;
  154.     
  155.     errno = 0;
  156.     num = strtol(*p, &endp, 10);
  157.     if (*p == endp || errno != 0) return -1;
  158.     while (*endp == ' ' || *endp == '\t') endp++;
  159.     *p = endp;
  160.     return num;
  161. }
  162.  
  163.  
  164.  
  165. /*----------------------------------------------------------------------------
  166.     SendServerCommand 
  167.     
  168.     Send a command to the server.
  169.     
  170.     Entry:    gBuffer = 0-terminated command.
  171.     
  172.     Exit:    function result = true if no error, false if error.
  173.     
  174.     The command should be single line without a terminating CR or LF. This
  175.     function appends the CRLF before sending the command to the server.
  176. ----------------------------------------------------------------------------*/
  177.  
  178. static Boolean SendServerCommand (void)
  179. {
  180.     OSErr err;
  181.  
  182.     strcat(gBuffer, CRLF);
  183.     err = SendData(gConnID, gBuffer, strlen(gBuffer));
  184.     GetDateTime(&gLastNewsServerTransactionTime);
  185.     if (err == noErr) return true;
  186.     UnexpectedErrorMessage(err);
  187.     AbortNewsConnection();
  188.     return false;
  189. }
  190.  
  191.  
  192.  
  193. /*----------------------------------------------------------------------------
  194.     GetShortServerResponse 
  195.     
  196.     Gets a one line response from the server.
  197.     
  198.     Exit:    function result = true if no error, false if error.
  199.             gBuffer = server response.
  200.             *length = length of server response.
  201.             *serverCode = server response code.
  202. ----------------------------------------------------------------------------*/
  203.  
  204. static Boolean GetShortServerResponse (unsigned short *length, long *serverCode)
  205. {
  206.     OSErr err;
  207.     unsigned short len;
  208.     unsigned short next = 0;
  209.     Boolean done = false;
  210.     char *p;
  211.     
  212.     while (!done) {
  213.         len = kBufLen - next;
  214.         err = RecvData(gConnID, gBuffer + next, &len, true);
  215.         GetDateTime(&gLastNewsServerTransactionTime);
  216.         if (err != noErr) {
  217.             UnexpectedErrorMessage(err);
  218.             AbortNewsConnection();
  219.             return false;
  220.         }
  221.         next += len;
  222.         done = gBuffer[next-1] == LF;
  223.     }
  224.     *length = next;
  225.     p = gBuffer;
  226.     *serverCode = ParseNum(&p);
  227.     return true;
  228. }
  229.  
  230.  
  231.  
  232. /*----------------------------------------------------------------------------
  233.     GetBigServerResponse 
  234.     
  235.     Gets a multi-line response from the server. The response is terminated
  236.     with a final "." line.
  237.     
  238.     Exit:    function result = true if no error, false if error.
  239.             *serverCode = server result code on first response line.
  240.             *response = handle to response text.
  241.             *responseLength = length of response.
  242.             *numLinesReceived = number of lines received, not counting the
  243.                 initial server message line or the final "." line.
  244. ----------------------------------------------------------------------------*/
  245.  
  246. static Boolean GetBigServerResponse (long *serverCode,
  247.     Handle *response, long *responseLength, short *numLinesReceived)
  248. {
  249.     Handle resp = nil;
  250.     long respLen = 0;
  251.     long respAlloc = kBufLen;
  252.     unsigned short length;
  253.     char *p, *pEnd;
  254.     short numReceived = 0;
  255.     OSErr err;
  256.     Boolean start = true;
  257.  
  258.     resp = MyNewHandle(kBufLen);
  259.     while (true) {
  260.         length = kBufLen;
  261.         err = RecvData(gConnID, gBuffer, &length, true);
  262.         GetDateTime(&gLastNewsServerTransactionTime);
  263.         if (err != noErr) {
  264.             MyDisposHandle(resp);
  265.             AbortNewsConnection();
  266.             UnexpectedErrorMessage(err);
  267.             return false;
  268.         }
  269.         if (respLen + length > respAlloc) {
  270.             respAlloc += kBufLen;
  271.             MySetHandleSize(resp, respAlloc);
  272.         }
  273.         BlockMove(gBuffer, *resp + respLen, length);
  274.         respLen += length;
  275.         pEnd = gBuffer + length;
  276.         for (p = gBuffer; p < pEnd; p++) if (*p == LF) numReceived++;
  277.         if (start && numReceived > 0) {
  278.             HLock(resp);
  279.             p = *resp;
  280.             *serverCode = ParseNum(&p);
  281.             HUnlock(resp);
  282.             if (!(100 <= *serverCode && *serverCode <= 299)) break;
  283.             start = false;
  284.         }
  285.         if (respLen >= 5) {
  286.             p = *resp + respLen - 5;
  287.             if (*p == CR && *(p+1) == LF && *(p+2) == '.' && 
  288.                 *(p+3) == CR && *(p+4) == LF) break;
  289.         }
  290.     }
  291.     MySetHandleSize(resp, respLen);
  292.     *response = resp;
  293.     *responseLength = respLen;
  294.     *numLinesReceived = numReceived - 2;
  295.     return true;
  296. }
  297.  
  298.  
  299.  
  300. /*----------------------------------------------------------------------------
  301.     SendGroupCommand 
  302.     
  303.     Sends a GROUP command to the server.
  304.     
  305.     Entry:    groupName = name of group.
  306.     
  307.     Exit:    function result =
  308.                 0 if no error.
  309.                 1 if group does not exist.
  310.                 2 if some other error.
  311.             gBuffer = server response.
  312. ----------------------------------------------------------------------------*/
  313.  
  314. static short SendGroupCommand (char *groupName)
  315. {
  316.     unsigned short length;
  317.     long serverCode;
  318.  
  319.     *gCurGroup = 0;
  320.     strcpy(gBuffer,"GROUP ");
  321.     strcat(gBuffer, groupName);
  322.     if (gPrefs.logActionsToFile) LogGroupCommand(gBuffer);
  323.     if (!SendServerCommand()) return 2;
  324.     if (!GetShortServerResponse(&length, &serverCode)) return 2;
  325.     if (gPrefs.logActionsToFile) LogGroupCommandResponse(gBuffer, length);
  326.     GetDateTime(&gLastNewsServerTransactionTime);
  327.     if (serverCode == 411) return 1;
  328.     if (serverCode == 211) {
  329.         strcpy(gCurGroup, groupName);
  330.         return 0;
  331.     }
  332.     NewsServerErrorMessage("GROUP", gBuffer);
  333.     return 2;
  334. }
  335.  
  336.  
  337.  
  338. /*----------------------------------------------------------------------------
  339.     SetGroup 
  340.     
  341.     Sets the server group. A GROUP command is sent to the server only if 
  342.     necessary.
  343.     
  344.     Entry:    groupName = name of group.
  345.     
  346.     Exit:    function result =
  347.                 0 if no error.
  348.                 1 if group does not exist.
  349.                 2 if some other error.
  350. ----------------------------------------------------------------------------*/
  351.  
  352. static short SetGroup (char *groupName)
  353. {
  354.     if (strcasecmp(gCurGroup, groupName) == 0) return 0;
  355.     return SendGroupCommand(groupName);
  356. }
  357.  
  358.  
  359.  
  360. /*----------------------------------------------------------------------------
  361.     SendModeReader 
  362.     
  363.     Send "MODE READER" command (for INN).
  364.     
  365.     Exit:    function result = true if no error, false if error or canceled.
  366.     
  367.     Server error codes are not checked, just in case we might be talking to
  368.     a non-INN server.
  369. ----------------------------------------------------------------------------*/
  370.  
  371. static Boolean SendModeReader (void)
  372. {
  373.     unsigned short length;
  374.     long serverCode;
  375.  
  376.     strcpy(gBuffer,"MODE READER");
  377.     if (!SendServerCommand()) return false;
  378.     if (!GetShortServerResponse(&length, &serverCode)) return false;
  379.     return true;
  380. }
  381.  
  382.  
  383.  
  384. /*----------------------------------------------------------------------------
  385.     GetHello 
  386.     
  387.     Gets the initial greeting message back from a newly opened NNTP connection.
  388.     
  389.     Exit:    function result = true if no error, false if error or canceled.
  390.             gHello = saved server hello message.
  391. ----------------------------------------------------------------------------*/
  392.  
  393. static Boolean GetHello (void)
  394. {
  395.     unsigned short length;
  396.     long serverCode;
  397.         
  398.     if (!GetShortServerResponse(&length, &serverCode)) return false;
  399.     if (serverCode != 200 && serverCode != 201) {
  400.         NewsServerErrorMessage(nil, gBuffer);
  401.         return false;
  402.     }
  403.     gBuffer[length] = 0;
  404.     if (length > 255) length = 255;
  405.     while ((gBuffer[length-1] == CR || gBuffer[length-1] == LF) && length > 0) length--;
  406.     gBuffer[length] = 0;
  407.     strcpy(gHello, gBuffer);
  408.     if (!gPrefs.noModeReader) return SendModeReader();
  409.     return true;
  410. }
  411.  
  412.  
  413.  
  414. /*----------------------------------------------------------------------------
  415.     OpenNewsConnection 
  416.     
  417.     Open a connection to the remote NNTP server.
  418.     
  419.     Exit:    function result = true if no error, false if error or canceled.
  420.             gConnID = connection id of NNTP stream.
  421. ----------------------------------------------------------------------------*/
  422.  
  423. static Boolean OpenNewsConnection (void)
  424. {
  425.     unsigned long recvLen;
  426.     OSErr err;
  427.  
  428.     if (gCancel) return false;    
  429.     *gCurGroup = 0;
  430.     recvLen = kBufLen;
  431.     err = CreateStream(&gConnID, recvLen);
  432.     if (err != noErr) {
  433.         if (gConnID != 0) ReleaseStream(gConnID);
  434.         gConnID = 0;
  435.         UnexpectedErrorMessage(err);
  436.         return false;
  437.     }
  438.     err = OpenConnection(gConnID, gNNTPAddr, kNNTPPort, 30);
  439.     if (err != noErr) {
  440.         ReleaseStream(gConnID);
  441.         gConnID = 0;
  442.         if (err != -1) ErrorMessage("Could not open connection to news server.");
  443.         return false;
  444.     }
  445.     return GetHello();
  446. }
  447.  
  448.  
  449.  
  450. /*----------------------------------------------------------------------------
  451.     CloseNewsConnection 
  452.     
  453.     Close a connection to the remote NNTP server.
  454.     
  455.     Entry:    gConnID = connection id of NNTP stream.
  456.     
  457.     Exit:    function result = true if no error, false if error or canceled.
  458.             gConnID = 0.
  459. ----------------------------------------------------------------------------*/
  460.  
  461. static void CloseNewsConnection (void)
  462. {
  463.     unsigned short length;
  464.     OSErr err;
  465.     char *p;
  466.     long serverCode;
  467.     
  468.     if (gConnID == 0) return;
  469.     strcpy(gBuffer,"QUIT");
  470.     strcat(gBuffer,CRLF);
  471.     err = SendData(gConnID, gBuffer, strlen(gBuffer));
  472.     if (err != noErr) goto exit;
  473.     length = kBufLen;
  474.     err = RecvData(gConnID, gBuffer, &length, true);
  475.     if (err != noErr) goto exit;
  476.     p = gBuffer;
  477.     serverCode = ParseNum(&p);
  478.     if (serverCode != 205) goto exit;
  479.     err = CloseConnection(gConnID, true);
  480.     if (err != noErr) goto exit;
  481.     err = ReleaseStream(gConnID);    
  482.     if (err != noErr) goto exit;
  483.     gConnID = 0;
  484.     return;
  485.     
  486. exit:
  487.     AbortNewsConnection();
  488. }
  489.  
  490.  
  491.  
  492. /*----------------------------------------------------------------------------
  493.     ResetConnection 
  494.     
  495.     Checks to make sure that the nntp connection is still
  496.     established. If not, it tries to reestablish the connection.
  497.     
  498.     Entry:    gConnID = connection id of NNTP stream.
  499.     
  500.     Exit:    function result = true if no error, false if error or canceled.
  501. ----------------------------------------------------------------------------*/
  502.  
  503. static Boolean ResetConnection (void)
  504. {
  505.     byte state = 0;
  506.     
  507.     if (gCancel) return false;
  508.     if (gConnID != 0) {
  509.         GetConnectionState(gConnID, &state);
  510.         if (state == 8) return true;
  511.         AbortNewsConnection();
  512.     }
  513.     return OpenNewsConnection();
  514. }
  515.  
  516.  
  517. /*----------------------------------------------------------------------------
  518.     EstablishNewConnection 
  519.     
  520.     Closes and reopens the news server connection (for non-INN servers
  521.     which only read the active file at the initial connection).
  522.  
  523.     Entry:    gConnID = connection id of old NNTP stream.
  524.     
  525.     Exit:    function result = true if no error, false if error or canceled.
  526.             gConnID = connection id of new NNTP stream.
  527. ----------------------------------------------------------------------------*/
  528.  
  529. static Boolean EstablishNewConnection (void)
  530. {
  531.     /* If noNewConnection prefs set or startup time, don't close old connection - 
  532.        create a new one only if there is no current one. */
  533.  
  534.     if (gPrefs.noNewConnection || gStartingUp) return ResetConnection();
  535.     
  536.     /* Close old connection and open new one. */
  537.     
  538.     CloseNewsConnection();
  539.     return OpenNewsConnection();
  540. }
  541.  
  542.  
  543.  
  544. /*----------------------------------------------------------------------------
  545.     StartNNTP 
  546.     
  547.     Initializes MacTCP and opens the NNTP connection.
  548.     
  549.     Exit:    function result = true if no error, false if error or canceled.
  550. ----------------------------------------------------------------------------*/
  551.  
  552. Boolean StartNNTP (void)
  553. {
  554.     OSErr err;
  555.     
  556.     if (!InitMacTCP()) return false;
  557.     if (!GetNNTPAddr()) return false;
  558.     StatusWindow("Opening news server connection.");
  559.     if (!OpenNewsConnection()) return false;
  560.     return true;
  561. }
  562.  
  563.  
  564.  
  565. /*----------------------------------------------------------------------------
  566.     EndNNTP 
  567.     
  568.     Closes the connection to the NNTP server.
  569. ----------------------------------------------------------------------------*/
  570.  
  571. void EndNNTP (void)
  572. {
  573.     if (gConnID != 0) {
  574.         StatusWindow("Closing news server connection.");
  575.         CloseNewsConnection();
  576.     }
  577. }
  578.  
  579.  
  580.  
  581. /*----------------------------------------------------------------------------
  582.     GetGroupNames
  583.     
  584.     Gets a list of group names from the server and calls a processing 
  585.     function for each group name in the list.
  586.     
  587.     Entry:    lastTime = 0: Fetch the entire full group list.
  588.             lastTime != 0: Fetch just the groups which have been created
  589.                 since lastTime - 36 hours.
  590.             func = pointer to function to call for each group.
  591.     
  592.     Exit:    function result = true if no error, false if error or canceled.
  593.     
  594.     The group processing function "func" should be declared as follows:
  595.     
  596.     Boolean func (const char *name)
  597.  
  598.     Entry:     name = group name.
  599.     
  600.     Exit:    function result = true if no error, false if error or canceled.
  601.     
  602.     This function should call GiveTime to give time to other processes and
  603.     check for user cancel operations.
  604. ----------------------------------------------------------------------------*/
  605.  
  606. Boolean GetGroupNames (unsigned long lastTime, NameFunc func)
  607. {
  608.     DateTimeRec timeRec;
  609.     long expectedServerCode, serverCode;
  610.     Handle response = nil;
  611.     long responseLength;
  612.     char *p, *q;
  613.     short numLinesReceived;
  614.     char *serverCommand;
  615.  
  616.     if (!ResetConnection()) return false;
  617.     
  618.     /* Build the server command. */
  619.  
  620.     if (lastTime == 0) {
  621.     
  622.         /* Get the whole list */
  623.         
  624.         strcpy(gBuffer, "LIST");
  625.         serverCommand = "LIST";
  626.         
  627.     } else {
  628.     
  629.         /* Just get groups added since "lastTime". Subtract 36 hours to compensate 
  630.            for clock drift, daylight savings time, and server and client in 
  631.            different time zones. */
  632.            
  633.         lastTime -= 60L*60L*36L;
  634.         Secs2Date(lastTime, &timeRec);
  635.         timeRec.year = timeRec.year % 100;    /* Only want last two digits */
  636.         sprintf(gBuffer, "NEWGROUPS %02d%02d%02d %02d%02d%02d", 
  637.                 timeRec.year, timeRec.month, timeRec.day,
  638.                 timeRec.hour, timeRec.minute, timeRec.second);
  639.         serverCommand = "NEWGROUPS";
  640.         
  641.     }
  642.     
  643.     /* Send the server command and get the server response. */
  644.  
  645.     if (!SendServerCommand()) return false;
  646.     if (!GetBigServerResponse(&serverCode, &response, &responseLength, 
  647.         &numLinesReceived)) return false;
  648.     expectedServerCode = lastTime == 0 ? 215 : 231;
  649.     if (serverCode != expectedServerCode) goto exit2;
  650.     
  651.     /* Process the response. */
  652.     
  653.     HLock(response);
  654.     p = *response;
  655.     while (numLinesReceived--) {
  656.         while (*p != LF) p++;
  657.         p++;
  658.         q = p;
  659.         while (*q != ' ' && *q != CR) q++;
  660.         *q = 0;
  661.         if (!(*func)(p)) goto exit1;
  662.     }
  663.     
  664.     MyDisposHandle(response);
  665.     return true;
  666.     
  667. exit1:
  668.     MyDisposHandle(response);
  669.     AbortNewsConnection();
  670.     return false;
  671.     
  672. exit2:
  673.     HLock(response);
  674.     NewsServerErrorMessage(serverCommand, *response);
  675.     MyDisposHandle(response);
  676.     return false;
  677. }
  678.  
  679.  
  680.  
  681. /*----------------------------------------------------------------------------
  682.     GetArticle 
  683.     
  684.     Gets the full text of one article from the NNTP server.
  685.     
  686.     Entry:    groupName = name of group, or nil if fetching by message id.
  687.             article = string containing article number or message id.
  688.             
  689.     Exit:    *text = handle to article text.
  690.             *textLength = length of article text.
  691.             function result =
  692.                 0 if no error.
  693.                 1 if article does not exist on server or group does not exist.
  694.                 2 if some other error.
  695.                 
  696.     Backspace-underscore and underscore-backspace sequences are filtered out
  697.     of the article, as are all non-printable characters except for TAB and CR.
  698.     Trailing blank lines are deleted. CRLF line terminators are mapped to CR.
  699.     Leading double-period characters on lines are mapped to single periods.
  700. ----------------------------------------------------------------------------*/
  701.  
  702. short GetArticle (char *groupName, char *article, Handle *text, long *textLength)
  703. {
  704.     short result;
  705.     long serverCode;
  706.     Handle response = nil;
  707.     long responseLength;
  708.     char *p, *pEnd, *q;
  709.     short numLinesReceived;
  710.     
  711.     if (!ResetConnection()) return 2;
  712.     
  713.     /* Set group if fetching by article number. */
  714.     
  715.     if (groupName != nil) {
  716.         result = SetGroup(groupName);
  717.         if (result != 0) return result;
  718.     }
  719.     
  720.     /* Send ARTICLE command. */
  721.     
  722.     strcpy(gBuffer, "ARTICLE ");
  723.     strcat(gBuffer, article);
  724.     if (!SendServerCommand()) return 2;
  725.     
  726.     /* Get server response. */
  727.     
  728.     if (!GetBigServerResponse(&serverCode, &response, 
  729.         &responseLength, &numLinesReceived)) return 2;
  730.     if (serverCode == 423 || serverCode == 430) {
  731.         MyDisposHandle(response);
  732.         return 1;
  733.     }
  734.     if (serverCode != 220) {
  735.         HLock(response);
  736.         NewsServerErrorMessage("ARTICLE", *response);
  737.         MyDisposHandle(response);
  738.         return 2;
  739.     }
  740.     
  741.     /* Process server response. */
  742.     
  743.     p = *response;
  744.     pEnd = *response + responseLength;
  745.     q = *response;
  746.     while (*p != LF) p++;    /* skip over initial 220 server response line */
  747.     p++;
  748.     while (p < pEnd) {
  749.         if (*p == '.' && *(p+1) == '.' && *(p-1) == LF) {
  750.             /* map ".." to "." at beginning of line. */
  751.             *q++ = '.';
  752.             p += 2;
  753.         } else if (*p == '\b' && *(p+1) == '_') {
  754.             /* filter out backspace-underscore */
  755.             p += 2;
  756.         } else if (*p == '_' && *(p+1) == '\b') {
  757.             /* filter out underscore-backspace */
  758.             p += 2;
  759.         } else if ((*p >= ' ' && *p <= '~') || *p == CR || *p == '\t') {
  760.             /* copy printable chars, CR, and TAB as is. */
  761.             *q++ = *p++;
  762.         } else {
  763.             /* filter out other unprintable chars, including LF. */
  764.             *p++;
  765.         }
  766.     }
  767.     q -= 3;   /* trim trailing "." and CR */
  768.     while (q > *response && *q == CR) q--;   /* trim trailing CR's */
  769.     q++;
  770.     responseLength = q - *response;
  771.     
  772.     /* Return. */
  773.     
  774.     if (responseLength <= 0) {
  775.         MyDisposHandle(response);
  776.         return 1;
  777.     } else {
  778.         MySetHandleSize(response, responseLength);
  779.         *text = response;
  780.         *textLength = responseLength;
  781.         return 0;
  782.     }
  783. }
  784.  
  785.  
  786.  
  787. /*----------------------------------------------------------------------------
  788.     PostArticle 
  789.     
  790.     Posts a news article.
  791.     
  792.     Entry:    text = pointer to new article text.
  793.             textLength = length of text.
  794.             
  795.     Exit:    function result = true if no error, false if error or canceled.
  796.     
  797.     The caller must prepare the message text properly: It must begin with the
  798.     full header, followed by an empty line, followed by the body. All lines
  799.     must be terminated with CRLF. Periods at the beginnings of lines must
  800.     be replaced by double-periods.
  801. ----------------------------------------------------------------------------*/
  802.  
  803. Boolean PostArticle (char *text, unsigned short textLength)
  804. {
  805.     unsigned short length;
  806.     long serverCode;
  807.     OSErr err;
  808.     
  809.     if (!ResetConnection()) return false;
  810.     
  811.     /* Send the POST command. */
  812.     
  813.     strcpy(gBuffer,"POST");
  814.     if (!SendServerCommand()) return false;
  815.     if (!GetShortServerResponse(&length, &serverCode)) return false;
  816.     if (serverCode != 340) goto exit2;
  817.  
  818.     /* Send the article. */
  819.  
  820.     err = SendData(gConnID, text, textLength);
  821.     GetDateTime(&gLastNewsServerTransactionTime);
  822.     if (err != noErr) goto exit1;
  823.     
  824.     /* Send the terminating "." on a line by itself */
  825.  
  826.     strcpy(gBuffer, CRLF);
  827.     strcat(gBuffer, ".");
  828.     if (!SendServerCommand()) return false;
  829.     if (!GetShortServerResponse(&length, &serverCode)) return false;
  830.     if (serverCode != 240) goto exit2;
  831.  
  832.     return true;
  833.  
  834. exit1:
  835.     UnexpectedErrorMessage(err);
  836.     AbortNewsConnection();
  837.     return false;
  838.  
  839. exit2:
  840.  
  841.     NewsServerErrorMessage("POST", gBuffer);
  842.     return false;
  843. }
  844.  
  845.  
  846.  
  847. /*----------------------------------------------------------------------------
  848.     GetGroupArticleRange 
  849.     
  850.     Queries the NNTP server to get the current article range for a single group.
  851.     
  852.     Entry:    theGroup = pointer to group record.
  853.     
  854.     Exit:    function result =
  855.                 0 if no error.
  856.                 1 if group does not exist.
  857.                 2 if some other error.
  858.             theGroup->firstMess = first article in range.
  859.             theGroup->lastMess = last article in range.
  860.             theGroup->numUnread = number of articles in range.
  861. ----------------------------------------------------------------------------*/
  862.  
  863. short GetGroupArticleRange (TGroup *theGroup)
  864. {
  865.     short result;
  866.     char *p;
  867.     CStr255 groupName;
  868.     long first, last;
  869.  
  870.     if (!ResetConnection()) return 2;
  871.     
  872.     strcpy(groupName, *gGroupNames + theGroup->nameOffset);
  873.     result = SendGroupCommand(groupName);
  874.     if (result != 0) return result;
  875.  
  876.     p = gBuffer;
  877.     ParseNum(&p);            /* Skip server response code */
  878.     ParseNum(&p);            /* Skip number of articles estimate */
  879.     first = ParseNum(&p);
  880.     last = ParseNum(&p);
  881.     if (first == 0 && last == 0) {
  882.         /* Special case empty group: set firstMess = lastMess + 1. */
  883.         theGroup->firstMess = theGroup->lastMess + 1;
  884.     } else {
  885.         if (first <= 0) first = 1;
  886.         if (last < first) last = first - 1;
  887.         theGroup->firstMess = first;
  888.         theGroup->lastMess = last;
  889.     } 
  890.     theGroup->numUnread = theGroup->lastMess - theGroup->firstMess + 1;
  891.  
  892.     return 0;
  893. }
  894.  
  895.  
  896.  
  897. /*----------------------------------------------------------------------------
  898.     GetGroupArrayArticleRangesOneAtATime 
  899.     
  900.     Queries the NNTP server to get current article range info for designated 
  901.     groups in a group list array. The GROUP commands are sent one at a time
  902.     (not batched). This function is used for servers which do not support
  903.     batched commands.
  904.  
  905.     Entry:    groupArray = handle to group array.
  906.             numGroups = number of groups in group array.
  907.  
  908.             Each group in the array to be updated is marked with status='x'.
  909.  
  910.     Exit:    function result = true if no error, false if error or canceled.
  911.             Deleted groups are marked with status='d'.
  912. ----------------------------------------------------------------------------*/
  913.  
  914. static Boolean GetGroupArrayArticleRangesOneAtATime (TGroup **groupArray, 
  915.     short numGroups)
  916. {
  917.     TGroup *g, *gEnd;
  918.     short result;
  919.     
  920.     if (gPrefs.logActionsToFile) LogGetGroupArrayArticleRangesOneAtATimeBegin();
  921.     HLock((Handle)groupArray);
  922.     g = *groupArray;
  923.     gEnd = g + numGroups;
  924.     while (g < gEnd) {
  925.         if (g->status == 'x') {
  926.             result = GetGroupArticleRange(g);
  927.             if (result == 2) return false;
  928.             if (result == 1) g->status = 'd';
  929.         }
  930.         g++;
  931.     }
  932.     HUnlock((Handle)groupArray);
  933.     if (gPrefs.logActionsToFile) LogGetGroupArrayArticleRangesOneAtATimeEnd();
  934.     
  935.     return true;
  936. }
  937.  
  938.  
  939.  
  940. /*----------------------------------------------------------------------------
  941.     PartialGetGroupArrayArticleRanges 
  942.     
  943.     Queries the NNTP server to get current article range info for designated 
  944.     groups in a group list array.
  945.  
  946.     Entry:    groupArray = handle to group array.
  947.             numGroups = number of groups in group array.
  948.             *firstGroup = index in array of first group to check.
  949.  
  950.             Each group in the array to be updated is marked with status='x'.
  951.  
  952.     Exit:    function result = true if no error, false if error or canceled.
  953.             Deleted groups are marked with status='d'.
  954.             *firstGroup = index in array of next group to check.
  955.  
  956.     For a large number of groups, this function may only check a partial
  957.     collection of groups. In this case, the function should be called
  958.     repeatedly until firstGroup == numGroups. The reason for doing this
  959.     in pieces is because we cannot send more than 32K of data (GROUP commands)
  960.     to the server at once.
  961. ----------------------------------------------------------------------------*/
  962.  
  963. static Boolean PartialGetGroupArrayArticleRanges (TGroup **groupArray, 
  964.     short numGroups, short *firstGroup)
  965. {
  966.     long groupCmdsLen = 0;
  967.     unsigned short len;
  968.     short i;
  969.     CStr255 groupName;
  970.     CStr255 tmpStr;
  971.     OSErr err;
  972.     short numGroupsToCheck = 0;
  973.     Handle response = nil;
  974.     long responseLen = 0;
  975.     char *p, *pEnd;
  976.     TGroup *g;
  977.     char *lineStart;
  978.     long serverCode;
  979.     char *gName;
  980.     short fGroup;
  981.     long first, last;
  982.     long respAlloc = kBufLen;
  983.     short numReceived = 0;
  984.     Boolean start = true;
  985.     
  986.     /* Build the list of GROUP commands. */
  987.     
  988.     fGroup = *firstGroup;
  989.     for (i = fGroup; i < numGroups; i++) {
  990.         if ((*groupArray)[i].status == 'x') {
  991.             strcpy(groupName, *gGroupNames + (*groupArray)[i].nameOffset);
  992.             sprintf(tmpStr, "GROUP %s%s", groupName, CRLF);
  993.             len = strlen(tmpStr);
  994.             if (groupCmdsLen + len > kBufLen) break;
  995.             BlockMove(tmpStr, gBuffer + groupCmdsLen, len);
  996.             groupCmdsLen += len;
  997.             numGroupsToCheck++;
  998.         }
  999.     }
  1000.     *firstGroup = i;
  1001.     
  1002.     /* Send the GROUP commands to the server. */
  1003.     
  1004.     err = SendData(gConnID, gBuffer, groupCmdsLen);
  1005.     if (err != noErr) goto exit1;
  1006.     if (gPrefs.logActionsToFile) 
  1007.         LogBatchGroupCommandSend(gBuffer, groupCmdsLen, numGroupsToCheck);
  1008.     
  1009.     /* Read the responses from the server. */
  1010.  
  1011.     response = MyNewHandle(kBufLen);
  1012.     while (numReceived < numGroupsToCheck) {
  1013.         len = kBufLen;
  1014.         err = RecvData(gConnID, gBuffer, &len, true);
  1015.         GetDateTime(&gLastNewsServerTransactionTime);
  1016.         if (err != noErr) goto exit3;
  1017.         if (responseLen + len > respAlloc) {
  1018.             respAlloc += kBufLen;
  1019.             MySetHandleSize(response, respAlloc);
  1020.         }
  1021.         BlockMove(gBuffer, *response + responseLen, len);
  1022.         responseLen += len;
  1023.         pEnd = gBuffer + len;
  1024.         for (p = gBuffer; p < pEnd; p++) if (*p == LF) numReceived++;
  1025.         if (gPrefs.logActionsToFile) LogBatchGroupCommandResponse(gBuffer, len, numReceived);
  1026.     }
  1027.     if (gPrefs.logActionsToFile) LogBatchGroupCommandsFinished();
  1028.     MySetHandleSize(response, responseLen);
  1029.     
  1030.     /* Process the responses.
  1031.        
  1032.        Valid responses are:
  1033.        
  1034.        211 num first last groupName
  1035.        411 no such news group
  1036.     */
  1037.     
  1038.     HLock(response);
  1039.     HLock((Handle)groupArray);
  1040.     HLock(gGroupNames);
  1041.     p = *response;
  1042.     pEnd = p + responseLen;
  1043.     g = *groupArray + fGroup;
  1044.     while (p < pEnd) {
  1045.         lineStart = p;
  1046.         serverCode = ParseNum(&p);
  1047.         if (serverCode != 211 && serverCode != 411) goto exit2;
  1048.         while (g->status != 'x') g++;
  1049.         if (serverCode == 211) {
  1050.             ParseNum(&p);
  1051.             first = ParseNum(&p);
  1052.             last = ParseNum(&p);
  1053.             if (first == 0 && last == 0) {
  1054.                 /* Special case empty group: set firstMess = lastMess + 1. */
  1055.                 g->firstMess = g->lastMess + 1;
  1056.             } else {
  1057.                 if (first <= 0) first = 1;
  1058.                 if (last < first) last = first - 1;
  1059.                 g->firstMess = first;
  1060.                 g->lastMess = last;
  1061.             } 
  1062.             g->numUnread = g->lastMess - g->firstMess + 1;
  1063.             gName = *gGroupNames + g->nameOffset;
  1064.             len = strlen(gName);
  1065.             if (strncasecmp(p, gName, len) != 0) goto exit2;
  1066.             p += len;
  1067.             if (*p != ' ' && *p != CR && *p != LF) goto exit2;
  1068.         } else {
  1069.             g->status = 'd';
  1070.         }
  1071.         g++;
  1072.         while (*p != LF) p++;
  1073.         p++;
  1074.     }
  1075.     HUnlock((Handle)groupArray);
  1076.     HUnlock(gGroupNames);
  1077.     MyDisposHandle(response);
  1078.     
  1079.     return true;
  1080.     
  1081. exit1:
  1082.     UnexpectedErrorMessage(err);
  1083.     AbortNewsConnection();
  1084.     goto exit3; 
  1085.     
  1086. exit2:
  1087.     NewsServerErrorMessage("GROUP", lineStart);
  1088.     
  1089. exit3:
  1090.     HUnlock((Handle)groupArray);
  1091.     HUnlock(gGroupNames);
  1092.     MyDisposHandle(response);
  1093.     return false;
  1094. }
  1095.  
  1096.  
  1097.  
  1098. /*----------------------------------------------------------------------------
  1099.     GetGroupArrayArticleRanges 
  1100.     
  1101.     Queries the NNTP server to get current article range info for designated 
  1102.     groups in a group list array.
  1103.  
  1104.     Entry:    groupArray = handle to group array.
  1105.             numGroups = number of groups in group array.
  1106.  
  1107.             Each group in the array to be updated is marked with status='x'.
  1108.  
  1109.     Exit:    function result = true if info retrieved, false if error.
  1110.             Deleted groups are marked with status='d'.
  1111. ----------------------------------------------------------------------------*/
  1112.  
  1113. Boolean GetGroupArrayArticleRanges (TGroup **groupArray, short numGroups)
  1114. {
  1115.     short firstGroup = 0;
  1116.  
  1117.     if (!EstablishNewConnection()) return false;
  1118.  
  1119.     if (gPrefs.batchedGroupCmds) {
  1120.         *gCurGroup = 0;
  1121.         while (firstGroup < numGroups) { 
  1122.             if (!PartialGetGroupArrayArticleRanges(groupArray, 
  1123.                 numGroups, &firstGroup)) return false;
  1124.         }
  1125.         return true;
  1126.     } else {
  1127.         return GetGroupArrayArticleRangesOneAtATime(groupArray, numGroups);
  1128.     }
  1129. }
  1130.  
  1131.  
  1132.  
  1133. /*----------------------------------------------------------------------------
  1134.     SortHeadersCompare 
  1135.     
  1136.     This is the comparison function used to sort a header array into 
  1137.     increasing order by article number.
  1138.     
  1139.     Entry:    p = pointer to first THeader struct.
  1140.             q = pointer to second THeader struct.
  1141.             
  1142.     Exit:    function result =
  1143.                 -1 if p->number < q->number
  1144.                  0 if p->number == q->number
  1145.                  1 if p->number > q->number
  1146. ----------------------------------------------------------------------------*/
  1147.  
  1148. static short SortHeadersCompare (THeader *p, THeader *q)
  1149. {
  1150.     static short Counter = 0;
  1151.  
  1152.     if((++Counter & 0x1f) == 0) {
  1153.         GiveTime();
  1154.         Counter = 0;
  1155.     }
  1156.     
  1157.     if (p->number < q->number) {
  1158.         return -1;
  1159.     } else if (p->number == q->number) {
  1160.         return 0;
  1161.     } else {
  1162.         return 1;
  1163.     }
  1164. }
  1165.  
  1166.  
  1167.  
  1168. /*----------------------------------------------------------------------------
  1169.     ReadHeaders
  1170.     
  1171.     Parses the response from an XHDR or XPAT server command.
  1172.  
  1173.     Entry:    strings = handle to strings block, or nil to return only
  1174.                 article numbers.
  1175.             *nextStringOffset = offset of next available location in
  1176.                 strings block, or nil if returning only article 
  1177.                 numbers.
  1178.             func = pointer to header munging function, or nil if none.
  1179.             maxStringLen = maximum string length (if strings != nil).
  1180.             serverCommand = "XHDR" or "XPAT".
  1181.             
  1182.     Exit:    function result = 
  1183.                 0 if no error.
  1184.                 2 if error.
  1185.                 3 if server does not support the command.
  1186.             *headers = handle to array of THeader records, sorted into
  1187.                 increasing order by article number.
  1188.             *numHeaders = number of headers.
  1189.             *nextStringOffset updated.
  1190.             msg = server error message if server does not support the command.
  1191.     
  1192.     The header munging function "func" should be declared as follows:
  1193.     
  1194.     short func (char *header)
  1195.  
  1196.     Entry:     header = header string.
  1197.     
  1198.     Exit:    header = modified header string.
  1199.             function result = length of modified header string.
  1200.     
  1201.     The modified header string must be shorter than or the same length as
  1202.     the original header string.
  1203. ----------------------------------------------------------------------------*/
  1204.  
  1205. static short ReadHeaders (Handle strings, long *nextStringOffset,
  1206.      HeaderMunge func, short maxStringLen, char *serverCommand, 
  1207.      THeader ***headers, short *numHeaders, CStr255 msg)
  1208. {
  1209.     long serverCode;
  1210.     Handle response;
  1211.     long responseLength;
  1212.     short numLinesReceived;
  1213.     THeader **theHeaders;
  1214.     long strNext, strAllocated, strLen;
  1215.     char *p, *q, *r;
  1216.     THeader *h;
  1217.     Boolean sorted = true;
  1218.     long prevNumber = 0;
  1219.     
  1220.     /* Read the server response. */
  1221.  
  1222.     if (!GetBigServerResponse(&serverCode, 
  1223.         &response, &responseLength, &numLinesReceived)) return 2;
  1224.     if (serverCode == 500) {
  1225.         BlockMove(*response, msg, responseLength <= 256 ? responseLength : 256);
  1226.         MyDisposHandle(response);
  1227.         return 3;
  1228.     }
  1229.     if (serverCode != 221) {
  1230.         HLock(response);
  1231.         NewsServerErrorMessage(serverCommand, *response);
  1232.         MyDisposHandle(response);
  1233.         return 2;
  1234.     }
  1235.     
  1236.     /* Process the response. */
  1237.         
  1238.     theHeaders = (THeader**)MyNewHandle(numLinesReceived*sizeof(THeader));
  1239.     *headers = theHeaders;
  1240.     *numHeaders = numLinesReceived;
  1241.     if (strings != nil) {
  1242.         strNext = *nextStringOffset;
  1243.         strAllocated = GetHandleSize(strings);
  1244.     }
  1245.     HLock(response);
  1246.     HLock((Handle)theHeaders);
  1247.     p = *response;
  1248.     h = *theHeaders;
  1249.     while (*p != LF) p++;    /* skip over 221 server response */ 
  1250.     p++;
  1251.     while (numLinesReceived--) {
  1252.         h->number = ParseNum(&p);
  1253.         sorted = sorted && h->number >= prevNumber;
  1254.         q = p;
  1255.         while (*p != LF) p++;
  1256.         r = p-1;
  1257.         p++;
  1258.         while (r >= q && (*r == CR || *r == ' ' || *r == '\t')) r--;
  1259.         r++;
  1260.         *r = 0;
  1261.         if (strings != nil) {
  1262.             strLen = r - q + 1;
  1263.             if (strNext + strLen > strAllocated) {
  1264.                 strAllocated += 10000;
  1265.                 MySetHandleSize(strings, strAllocated);
  1266.             }
  1267.             BlockMove(q, *strings + strNext, strLen);
  1268.             if (func != nil) {
  1269.                 HLock(strings);
  1270.                 strLen = (*func)(*strings + strNext) + 1;
  1271.                 HUnlock(strings);
  1272.             }
  1273.             if (strLen > maxStringLen + 1) {
  1274.                 strLen = maxStringLen + 1;
  1275.                 *(*strings + strNext + strLen - 1) = 0;
  1276.             }
  1277.             h->offset = strNext;
  1278.             strNext += strLen;
  1279.         }
  1280.         h++;
  1281.     }
  1282.     MyDisposHandle(response);
  1283.     
  1284.     if (strings !=  nil) {
  1285.         *nextStringOffset = strNext;
  1286.     }
  1287.     
  1288.     if (!sorted) 
  1289.         if (!FastQSort(*theHeaders, *numHeaders, sizeof(THeader), 
  1290.             (short(*)(void*,void*))SortHeadersCompare)) goto exit;
  1291.     
  1292.     HUnlock((Handle)theHeaders);
  1293.     return 0;
  1294.  
  1295. exit:
  1296.  
  1297.     HUnlock((Handle)theHeaders);
  1298.     return 2;
  1299. }
  1300.  
  1301.  
  1302.  
  1303. /*----------------------------------------------------------------------------
  1304.     GetHeaders 
  1305.     
  1306.     Gets header lines from the news server.
  1307.  
  1308.     Entry:    groupName = the group name.
  1309.             headerName = the header name.
  1310.             first = first article number.
  1311.             last = last article number.
  1312.             strings = handle to strings block, or nil to return only
  1313.                 article numbers.
  1314.             *nextStringOffset = offset of next available location in
  1315.                 strings block.
  1316.             func = pointer to header munging function, or nil if none.
  1317.             maxStringLen = maximum string length (if strings != nil).
  1318.             
  1319.     Exit:    function result = 
  1320.                 0 if no error.
  1321.                 1 if group does not exist.
  1322.                 2 if some other error.
  1323.             *headers = handle to array of THeader records.
  1324.             *numHeaders = number of headers.
  1325.             *nextStringOffset updated.
  1326.     
  1327.     The header munging function "func" should be declared as follows:
  1328.     
  1329.     short func (char *header)
  1330.  
  1331.     Entry:     header = header string.
  1332.     
  1333.     Exit:    header = modified header string.
  1334.             function result = length of modified header string.
  1335.     
  1336.     The modified header string must be shorter than or the same length as
  1337.     the original header string.
  1338. ----------------------------------------------------------------------------*/
  1339.  
  1340. short GetHeaders (char *groupName, char *headerName, long first,
  1341.     long last, Handle strings, long *nextStringOffset,
  1342.     HeaderMunge func, short maxStringLen, THeader ***headers, short *numHeaders)
  1343. {
  1344.     short result;
  1345.     CStr255 msg;
  1346.     
  1347.     if (!ResetConnection()) return 2;
  1348.     
  1349.     result = SetGroup(groupName);
  1350.     if (result != 0) return result;
  1351.  
  1352.     sprintf(gBuffer, "XHDR %s %ld-%ld", headerName, first, last);
  1353.     if (!SendServerCommand()) return 2;
  1354.     
  1355.     result = ReadHeaders(strings, nextStringOffset, func, maxStringLen, 
  1356.         "XHDR", headers, numHeaders, msg);
  1357.     
  1358.     if (result != 3) return result;
  1359.     
  1360.     NewsServerErrorMessage("XHDR", msg);
  1361.     return 2;
  1362. }
  1363.  
  1364.  
  1365.  
  1366. /*----------------------------------------------------------------------------
  1367.     SearchHeadersTheHardWay 
  1368.     
  1369.     Searches header lines and returns matches. The search is for any 
  1370.     substring and is case-insensitive. This search function is called
  1371.     when the server does not support the XPAT command. In this case,
  1372.     we use the XHDR command to fetch all the headers and do the search
  1373.     ourselves.
  1374.  
  1375.     Entry:    groupName = the group name.
  1376.             headerName = the header name.
  1377.             first = first article number.
  1378.             last = last article number.
  1379.             pattern = search string.
  1380.             
  1381.     Exit:    function result = 
  1382.                 0 if no error.
  1383.                 1 if group does not exist.
  1384.                 2 if some other error.
  1385.             *headers = handle to array of THeader records.
  1386.             *numHeaders = number of headers.
  1387. ----------------------------------------------------------------------------*/
  1388.  
  1389. static short SearchHeadersTheHardWay (char *groupName, char *headerName, long first, long last,
  1390.     char *pattern, THeader ***headers, short *numHeaders)
  1391. {
  1392.     short result;
  1393.     Handle strings = nil;
  1394.     long nextStringOffset;
  1395.     THeader **theHeaders;
  1396.     short numTheHeaders, numMatchedHeaders;
  1397.     THeader *pHeader, *pHeaderEnd, *qHeader;
  1398.     short patternLen, strLen;
  1399.     char *pString, *pStringLast;
  1400.     Boolean match;
  1401.     
  1402.     /* Allocate a temporary memory block to hold the header strings. */
  1403.     
  1404.     strings = MyNewHandle(10000);
  1405.     nextStringOffset = 0;
  1406.     
  1407.     /* Fetch the headers. */
  1408.         
  1409.     result = GetHeaders(groupName, headerName, first, last, strings,
  1410.         &nextStringOffset, nil, 0x7fff, &theHeaders, &numTheHeaders);
  1411.     if (result != 0) {
  1412.         MyDisposHandle(strings);
  1413.         return result;
  1414.     }
  1415.     
  1416.     /* Walk through the header strings and delete the ones which don't
  1417.        match the pattern. */
  1418.     
  1419.     HLock(strings);
  1420.     HLock((Handle)theHeaders);
  1421.     patternLen = strlen(pattern);
  1422.     pHeaderEnd = *theHeaders + numTheHeaders;
  1423.     qHeader = *theHeaders;
  1424.     numMatchedHeaders = 0;
  1425.     for (pHeader = *theHeaders; pHeader < pHeaderEnd; pHeader++) {
  1426.         pString = *strings + pHeader->offset;
  1427.         strLen = strlen(pString);
  1428.         match = false;
  1429.         if (patternLen <= strLen) {
  1430.             pStringLast = pString + strLen - patternLen;
  1431.             for (; pString <= pStringLast; pString++) {
  1432.                 if (strncasecmp(pattern, pString, patternLen) == 0) {
  1433.                     match = true;
  1434.                     break;
  1435.                 }
  1436.             }
  1437.         }
  1438.         if (match) {
  1439.             *qHeader = *pHeader;
  1440.             qHeader++;
  1441.             numMatchedHeaders++;
  1442.         }
  1443.     } 
  1444.     
  1445.     /* Clean up and exit. */
  1446.     
  1447.     HUnlock((Handle)theHeaders);
  1448.     MySetHandleSize((Handle)theHeaders, numMatchedHeaders*sizeof(THeader));
  1449.     MyDisposHandle(strings);
  1450.     *headers = theHeaders;
  1451.     *numHeaders = numMatchedHeaders;
  1452.     return 0;
  1453. }
  1454.  
  1455.  
  1456.  
  1457. /*----------------------------------------------------------------------------
  1458.     SearchHeaders 
  1459.     
  1460.     Searches header lines and returns matches. The search is for any 
  1461.     substring and is case-insensitive. The XPAT command is used if the
  1462.     server supports it and the "use XPAT" preference is turned on. 
  1463.     Otherwise, we do it the hard way.
  1464.  
  1465.     Entry:    groupName = the group name.
  1466.             headerName = the header name.
  1467.             first = first article number.
  1468.             last = last article number.
  1469.             pattern = search string.
  1470.             
  1471.     Exit:    function result = 
  1472.                 0 if no error.
  1473.                 1 if group does not exist.
  1474.                 2 if some other error.
  1475.             *headers = handle to array of THeader records.
  1476.             *numHeaders = number of headers.
  1477. ----------------------------------------------------------------------------*/
  1478.  
  1479. short SearchHeaders (char *groupName, char *headerName, long first, long last,
  1480.     char *pattern, THeader ***headers, short *numHeaders)
  1481. {
  1482.     short result;
  1483.     char *p, *q;
  1484.     static Boolean haveXPAT = true;
  1485.     CStr255 msg;
  1486.     
  1487.     if (!haveXPAT || !gPrefs.useXPAT) 
  1488.         return SearchHeadersTheHardWay (groupName, headerName, first, last, 
  1489.             pattern, headers, numHeaders);
  1490.  
  1491.     if (!ResetConnection()) return 2;
  1492.     
  1493.     result = SetGroup(groupName);
  1494.     if (result != 0) return result;
  1495.     
  1496.     /* Build the XPAT command. Note the goofiness for the ignoreCase option because
  1497.        the XPAT command is case sensitive. Note also that we add 1 to the last
  1498.        article number, to avoid an error in inn 1.4 which never matches the last
  1499.        article in the range. */
  1500.  
  1501.     sprintf(gBuffer, "XPAT %s %ld-%ld ", headerName, first, last);
  1502.     q = gBuffer + strlen(gBuffer);
  1503.     *q++ = '*';
  1504.     for (p = pattern; *p; p++) {
  1505.         if (isupper(*p)) {
  1506.             *q++ = '[';
  1507.             *q++ = *p;
  1508.             *q++ = tolower(*p);
  1509.             *q++ = ']';
  1510.         } else if (islower(*p)) {
  1511.             *q++ = '[';
  1512.             *q++ = *p;
  1513.             *q++ = toupper(*p);
  1514.             *q++ = ']';
  1515.         } else if (*p == '*' || *p == '?' || *p == '\\' || *p == '[' || *p == ']') {
  1516.             *q++ = '\\';
  1517.             *q++ = *p;
  1518.         } else {
  1519.             *q++ = *p;
  1520.         }
  1521.     }
  1522.     *q++ = '*';
  1523.     *q++ = 0;
  1524.  
  1525.     /* Send the command and process the reply. */
  1526.  
  1527.     if (!SendServerCommand()) return 2;
  1528.     
  1529.     result = ReadHeaders(nil, nil, nil, 0, "XPAT", headers, numHeaders, msg);
  1530.     if (result != 3) return result;
  1531.     haveXPAT = false;
  1532.     return SearchHeadersTheHardWay (groupName, headerName, first, last, 
  1533.         pattern, headers, numHeaders);
  1534.  
  1535. }
  1536.  
  1537.  
  1538.  
  1539. /*----------------------------------------------------------------------------
  1540.     CloseIdleNewsConnectionCompletionRoutine
  1541.     
  1542.     This is the asynchronous completion routine used to close idle news
  1543.     server connections.
  1544.     
  1545.     This completion routine chains to itself to do the following tasks
  1546.     involved in gracefully tearing down the connection in the background:
  1547.     
  1548.     Wait for QUIT command send to complete (the send is initiated by the
  1549.         CloseIdleNewsConnection function below).
  1550.     Read incoming data until an error occurs (signalling that the
  1551.         news server has closed its end of the connection).
  1552.     Close our end of the connection.
  1553.     Release the stream.
  1554.     
  1555.     Entry:    Register A0 = pointer to closing news connection queue element.
  1556. ----------------------------------------------------------------------------*/
  1557.  
  1558. static void CloseIdleNewsConnectionCompletionRoutine (/* a0 = */ TCPiopb *iopb)
  1559. {
  1560.     TQueuedClosingNewsConnection *p;
  1561.     OSErr err;
  1562.     long savedA5;
  1563.  
  1564.     asm {
  1565.         move.l    a0,p
  1566.     }
  1567.     
  1568.     savedA5 = SetA5(p->myA5);
  1569.     
  1570.     if (p->pBlock.csCode == TCPSend) {
  1571.     
  1572.         /* The QUIT command has been sent. Start the first receive. */
  1573.         
  1574.         if (p->pBlock.ioResult != noErr) {
  1575.             p->destroy = true;
  1576.             SetA5(savedA5);
  1577.             return;
  1578.         }
  1579.         memset(&p->pBlock, 0, sizeof(TCPiopb));
  1580.         p->pBlock.ioCompletion = CloseIdleNewsConnectionCompletionRoutine;
  1581.         p->pBlock.ioCRefNum = p->ioCRefNum;
  1582.         p->pBlock.tcpStream = p->connID;
  1583.         p->pBlock.csCode = TCPRcv;
  1584.         p->pBlock.csParam.receive.commandTimeoutValue = 60;
  1585.         p->pBlock.csParam.receive.rcvBuff = (Ptr)&p->receiveBuf;
  1586.         p->pBlock.csParam.receive.rcvBuffLen = 256;
  1587.     
  1588.     } else if (p->pBlock.csCode == TCPRcv) {
  1589.     
  1590.         /* A receive operation has finished. If there was no error, start another
  1591.            receive. If an error occurred, it was because the server has closed
  1592.            its side of the connection. In this case, we start the TCPClose. */
  1593.        
  1594.            err = p->pBlock.ioResult;
  1595.         memset(&p->pBlock, 0, sizeof(TCPiopb));
  1596.         p->pBlock.ioCompletion = CloseIdleNewsConnectionCompletionRoutine;
  1597.         p->pBlock.ioCRefNum = p->ioCRefNum;
  1598.         p->pBlock.tcpStream = p->connID;
  1599.         if (err == noErr) {
  1600.             p->pBlock.csCode = TCPRcv;
  1601.             p->pBlock.csParam.receive.commandTimeoutValue = 60;
  1602.             p->pBlock.csParam.receive.rcvBuff = (Ptr)&p->receiveBuf;
  1603.             p->pBlock.csParam.receive.rcvBuffLen = 256;
  1604.         } else {
  1605.             p->pBlock.csCode = TCPClose;
  1606.             p->pBlock.csParam.send.ulpTimeoutValue = 60;
  1607.             p->pBlock.csParam.send.ulpTimeoutAction = 1;
  1608.             p->pBlock.csParam.send.validityFlags = 0xC0;
  1609.         }
  1610.     
  1611.     } else if (p->pBlock.csCode == TCPClose) {
  1612.     
  1613.         /* The close has finished. Start the release. */
  1614.  
  1615.         if (p->pBlock.ioResult != noErr) {
  1616.             p->destroy = true;
  1617.             SetA5(savedA5);
  1618.             return;
  1619.         }
  1620.         memset(&p->pBlock, 0, sizeof(TCPiopb));
  1621.         p->pBlock.ioCompletion = CloseIdleNewsConnectionCompletionRoutine;
  1622.         p->pBlock.ioCRefNum = p->ioCRefNum;
  1623.         p->pBlock.tcpStream = p->connID;
  1624.         p->pBlock.csCode = TCPRelease;
  1625.     
  1626.     } else if (p->pBlock.csCode == TCPRelease) {
  1627.     
  1628.         /* The release finished. Set the "finished" field to signal that we can
  1629.            now dispose of the queue element. */
  1630.            
  1631.         if (p->pBlock.ioResult != noErr) {
  1632.             p->destroy = true;
  1633.         } else {
  1634.             p->finished = true;
  1635.         }
  1636.         SetA5(savedA5);
  1637.         return;
  1638.         
  1639.     }
  1640.     
  1641.     /* Issue the next PBControl call in the chain. */
  1642.  
  1643.     err = PBControl((ParmBlkPtr)&p->pBlock, true);
  1644.     if (err != noErr) p->destroy = true;
  1645.     SetA5(savedA5);
  1646.     
  1647. }
  1648.  
  1649.  
  1650.  
  1651. /*----------------------------------------------------------------------------
  1652.     CloseIdleNewsConnection 
  1653.     
  1654.     This function is called by the main event loop. It checks to see if the
  1655.     current connection to the news server has been idle for 10 minutes. If
  1656.     it has, the connection is closed. This prevents idle server processes from
  1657.     using up process slots and swap space on the server.
  1658.     
  1659.     Unlike all the other TCP/IP transactions in NewsWatcher, this kind of
  1660.     connection closing must be done asynchronously. We use asynchronous
  1661.     PBControl calls with completion routine chaining to do the close.
  1662.     
  1663.     All the pending stream closes are linked together in a queue. This function
  1664.     also takes care of removing the queue elements and deallocating memory when
  1665.     the close operations have completed.
  1666.     
  1667.     Entry:    destroy = true if program termination. All pending closes are abruptly
  1668.             terminated.
  1669. ----------------------------------------------------------------------------*/
  1670.  
  1671. void CloseIdleNewsConnection (Boolean destroy)
  1672. {
  1673.     TQueuedClosingNewsConnection *p, *prev = nil, *next = nil;
  1674.     TCPiopb pb;
  1675.     unsigned long curTime;
  1676.     OSErr err;
  1677.  
  1678.     /* First take care of completed close operations, if any, and the destroy
  1679.        option. */
  1680.  
  1681.     for (p = gClosingNewsConnectionQueue; p != nil; p = next) {
  1682.         next = p->next;
  1683.         if (destroy || p->destroy || p->finished) {
  1684.             if (destroy || p->destroy) {
  1685.                 pb.ioCRefNum = GetTCPRefNum();
  1686.                 pb.tcpStream = gConnID;
  1687.                 pb.csCode = TCPRelease;
  1688.                 pb.tcpStream = p->pBlock.tcpStream;
  1689.                 PBControl((ParmBlkPtr)&pb, false);
  1690.                 MyDisposPtr(pb.csParam.create.rcvBuff);
  1691.             } else {
  1692.                 MyDisposPtr(p->pBlock.csParam.create.rcvBuff);
  1693.             }
  1694.             if (prev == nil) {
  1695.                 gClosingNewsConnectionQueue = next;
  1696.             } else {
  1697.                 prev->next = next;
  1698.             }
  1699.             MyDisposPtr((Ptr)p);
  1700.         } else {
  1701.             prev = p;
  1702.         }
  1703.     }
  1704.     if (destroy) return;
  1705.     
  1706.     /* Check to see if the current news server connection has been idle for at
  1707.        least 10 minutes. */
  1708.        
  1709.     if (gConnID == 0) return;
  1710.     GetDateTime(&curTime);
  1711.     if (curTime <= gLastNewsServerTransactionTime + 10*60) return;
  1712.     
  1713.     /* Allocate a new queue element. */
  1714.  
  1715.     p = (TQueuedClosingNewsConnection*)MyNewPtr(sizeof(TQueuedClosingNewsConnection));
  1716.     p->myA5 = SetCurrentA5();
  1717.     p->destroy = false;
  1718.     p->finished = false;
  1719.     p->next = gClosingNewsConnectionQueue;
  1720.     p->startingTick = TickCount();
  1721.     gClosingNewsConnectionQueue = p;
  1722.     p->pBlock.ioCompletion = CloseIdleNewsConnectionCompletionRoutine;
  1723.     p->pBlock.ioCRefNum = p->ioCRefNum = GetTCPRefNum();
  1724.     p->pBlock.tcpStream = p->connID = gConnID;
  1725.     
  1726.     /* Force a new connection to be opened on the next transaction. */
  1727.     
  1728.     gConnID = 0;
  1729.     
  1730.     /* Send an asynchronous "QUIT" command to the server, chained to our
  1731.        completion routine . */
  1732.  
  1733.     strcpy(p->sendBuf, "QUIT");
  1734.     strcat(p->sendBuf, CRLF);
  1735.     p->wds[0].length = 6;
  1736.     p->wds[0].ptr = (Ptr)&p->sendBuf;
  1737.     p->wds[1].length = 0;
  1738.     p->wds[1].ptr = nil;
  1739.     
  1740.     p->pBlock.csCode = TCPSend;
  1741.     p->pBlock.csParam.send.ulpTimeoutValue = 60;
  1742.     p->pBlock.csParam.send.ulpTimeoutAction = 1;
  1743.     p->pBlock.csParam.send.validityFlags = 0xC0;
  1744.     p->pBlock.csParam.send.wdsPtr = (Ptr)&p->wds;
  1745.  
  1746.     err = PBControl((ParmBlkPtr)&p->pBlock, true);
  1747.     if (err != noErr) p->destroy = true;
  1748. }
  1749.  
  1750.  
  1751.  
  1752. /*----------------------------------------------------------------------------
  1753.     GetServerInfoCopyHandler
  1754.     
  1755.     Handles the Edit menu Copy command for the get server info dialog.
  1756.         
  1757.     Entry:    dlg = pointer to dialog.
  1758. ----------------------------------------------------------------------------*/
  1759.  
  1760. static void GetServerInfoCopyHandler (DialogPtr dlg)
  1761. {
  1762.     ZeroScrap();
  1763.     HLock(gServerInfoText);
  1764.     PutScrap(GetHandleSize(gServerInfoText), 'TEXT', *gServerInfoText);
  1765.     HUnlock(gServerInfoText);
  1766.     TEFromScrap();
  1767. }
  1768.  
  1769.  
  1770.  
  1771. /*----------------------------------------------------------------------------
  1772.     GetServerInfo 
  1773.     
  1774.     Handles the "Get Server Info" command in the Special menu.
  1775. ----------------------------------------------------------------------------*/
  1776.  
  1777. void GetServerInfo (void)
  1778. {
  1779.     DialogPtr dlg;
  1780.     long serverCode;
  1781.     long responseLength;
  1782.     short numLinesReceived;
  1783.     char str[1000];
  1784.     long offset;
  1785.     char *p;
  1786.     short item;
  1787.     short fontNum;
  1788.     Handle serverHelpResponse = nil;
  1789.     Str255 dateStr, timeStr, versStr;
  1790.     CStr255 addrStr;
  1791.     unsigned long rawSecs;
  1792.     Handle vers1Resource;
  1793.  
  1794.     HiliteMenu(0);
  1795.  
  1796.     if (!ResetConnection()) return;
  1797.  
  1798.     GetDateTime(&rawSecs);
  1799.     IUDateString(rawSecs, abbrevDate, dateStr);
  1800.     IUTimeString(rawSecs, false, timeStr);
  1801.     vers1Resource = GetResource('vers', 1);
  1802.     pstrcpy(versStr, (StringPtr)*vers1Resource+6);
  1803.     sprintf(addrStr, "%ld.%ld.%ld.%ld", (gNNTPAddr >> 24) & 0xff, (gNNTPAddr >> 16) & 0xff, 
  1804.     (gNNTPAddr >> 8) & 0xff, gNNTPAddr & 0xff);
  1805.     sprintf(str, "%#s %#s\rNewsWatcher version %#s\rServer domain name: %#s\rServer IP address: %s\r\r%s\r\r", 
  1806.         dateStr, timeStr, versStr, gPrefs.newsServerName, addrStr, gHello);
  1807.  
  1808.     sprintf(gBuffer, "HELP%s", CRLF);
  1809.     if (!SendServerCommand()) return;
  1810.     if (!GetBigServerResponse(&serverCode, 
  1811.         &serverHelpResponse, &responseLength, &numLinesReceived)) return;
  1812.     for (offset = 0; offset >= 0; offset = Munger(serverHelpResponse, offset, LFSTR, 1, "", 0));
  1813.     p = *serverHelpResponse + GetHandleSize(serverHelpResponse) - 1;
  1814.     while (p > *serverHelpResponse && (*p == '.' || *p == CR)) p--;
  1815.     SetHandleSize(serverHelpResponse, p - *serverHelpResponse + 1);
  1816.         
  1817.     dlg = MyGetNewDialog(kServerInfoDlg);
  1818.     PtrToHand(str, &gServerInfoText, strlen(str));
  1819.     HLock(serverHelpResponse);
  1820.     HandAndHand(serverHelpResponse, gServerInfoText);
  1821.     HUnlock(serverHelpResponse);
  1822.     GetFNum("\pMonaco", &fontNum);
  1823.     SetItemReadOnly(dlg, kServerInfoTheInfoItem, gServerInfoText, fontNum, 9);
  1824.     MyDisposHandle(serverHelpResponse);
  1825.     
  1826.     SetDialogCustomCopyHandler(dlg, GetServerInfoCopyHandler);
  1827.  
  1828.     MyModalDialog(DialogFilter, &item, false, true);
  1829.     
  1830.     MyDisposDialog(dlg);
  1831.     MyDisposHandle(gServerInfoText);
  1832. }
  1833.